package searcher.gui;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Date;

import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;

import org.dom4j.Element;

import searcher.*;
import de.schlichtherle.io.File;
import de.schlichtherle.io.swing.JFileChooser;

/**
 * The graphical user interface of the Searcher program
 * 
 * @version $Id: SearcherGui.java,v 1.20 2007/09/05 19:48:20 abutler Exp $
 */
public class SearcherGui extends javax.swing.JFrame implements Runnable
{
	private static final java.text.SimpleDateFormat DATE_FORMAT;

	static
	{
		DATE_FORMAT = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm");
	}

	private File theFile;

	private Searcher theSearcher;

	private SearchCriteria theInclude;

	private java.util.List<SearchCriteria> theExcludes;

	SearchListener theExcludeListener;

	private JSplitPane theOutputSP;

	javax.swing.JTextArea theOutput;

	javax.swing.JTree theOutputTree;

	private JTextField theStatusBar;

	private JButton theSearchButton;

	private JButton theStopButton;

	private JButton thePauseButton;

	private Setup theSetup;

	private volatile boolean doStartSearch;

	private boolean printAllTextMatches;

	private Thread theSearchThread;

	private java.io.PrintStream thePS;

	/**
	 * Creates a SearcherGui
	 */
	public SearcherGui()
	{
		super("Searcher");
		setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
		String lnfName = javax.swing.UIManager.getSystemLookAndFeelClassName();
		try
		{
			javax.swing.UIManager.setLookAndFeel(lnfName);
		} catch(Exception e)
		{
			e.printStackTrace();
			System.err.println("Could not set look and feel: using default");
		}
		java.net.URL url = getClass().getResource("images/MagGlass.png");
		if(url != null)
			setIconImage(new javax.swing.ImageIcon(url).getImage());
		theFile = new File(new java.io.File("."));
		theSearcher = new Searcher();
		theInclude = new SearchCriteria(null, false, null, false);
		theInclude.addSearchListener(new IncludeSearchListener());
		theExcludes = new java.util.ArrayList<SearchCriteria>();
		theExcludeListener = new ExcludeSearchListener();

		JSplitPane mainSP = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
		JPanel bottomPanel = new JPanel();
		bottomPanel.setLayout(new BorderLayout());
		JPanel stopGoPanel = new JPanel();
		bottomPanel.add(stopGoPanel, BorderLayout.CENTER);
		stopGoPanel.setMaximumSize(new Dimension(1000000, 30));
		theSearchButton = new JButton("Search");
		stopGoPanel.add(theSearchButton);
		theSearchButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				doSearch();
			}
		});
		theStopButton = new JButton("Stop Search");
		stopGoPanel.add(theStopButton);
		theStopButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				stopSearch();
			}
		});
		theStopButton.setEnabled(false);
		thePauseButton = new JButton("Pause Search");
		stopGoPanel.add(thePauseButton);
		thePauseButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				if(((JButton) evt.getSource()).getText().equals("Pause Search"))
				{
					pauseSearch();
					((JButton) evt.getSource()).setText("Resume Search");
				}
				else
				{
					unpauseSearch();
					((JButton) evt.getSource()).setText("Pause Search");
				}
			}
		});
		thePauseButton.setEnabled(false);
		theStatusBar = new JTextField();
		theStatusBar.setBackground(mainSP.getBackground());
		theStatusBar.setMaximumSize(new Dimension(theStatusBar.getMaximumSize().width, 20));
		theStatusBar.setPreferredSize(new Dimension(theStatusBar.getPreferredSize().width, 20));
		theStatusBar.setEditable(false);
		bottomPanel.add(theStatusBar, BorderLayout.SOUTH);
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(mainSP, BorderLayout.CENTER);
		getContentPane().add(bottomPanel, BorderLayout.SOUTH);
		theSetup = new Setup();
		theSetup.setupMenuBar();
		javax.swing.JComponent sp = theSetup.createSettingsPanel();
		mainSP.add(sp);
		theOutputSP = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		mainSP.add(theOutputSP);
		theOutput = new javax.swing.JTextArea();
		theOutput.setLineWrap(true);
		theOutput.setWrapStyleWord(false);
		theOutput.setEditable(false);
		theOutputSP.setTopComponent(new JScrollPane(theOutput));
		theOutputTree = new javax.swing.JTree(new Object [0]);
		theOutputTree.setCellRenderer(new FileRenderer());
		theOutputSP.setBottomComponent(new JScrollPane(theOutputTree));

		java.io.PipedInputStream pis = new java.io.PipedInputStream();
		java.io.PipedOutputStream pos;
		try
		{
			pos = new java.io.PipedOutputStream(pis);
		} catch(java.io.IOException e)
		{
			throw new RuntimeException("Could not connect output to console", e);
		}
		new Thread(new TextAreaWriter(pis)).start();
		thePS = new java.io.PrintStream(pos);
		theSearcher.setPrintStream(thePS);

		theSearchThread = new Thread(this);
		theSearchThread.start();

		setSearchFile(".");
		setSize(750, 700);
		addWindowListener(new java.awt.event.WindowAdapter()
		{
			public void windowClosing(java.awt.event.WindowEvent evt)
			{
				try
				{
					saveState("defaultSearch.xml");
				} catch(IOException e)
				{
					System.err.println("Could not save search state");
					e.printStackTrace();
				}
				saveSize();
			}
		});
		if(new java.io.File("defaultSearch.xml").exists())
		{
			try
			{
				loadState("defaultSearch.xml");
			} catch(Exception e)
			{
				System.err.println("Could not load search state");
				e.printStackTrace();
			}
		}
		if(new java.io.File("size.xml").exists())
			loadSize();
	}

	public void setVisible(boolean b)
	{
		super.setVisible(b);
		theOutputSP.setDividerLocation(0.5);
	}

	/**
	 * @return The output stream that writes to the text pane
	 */
	public java.io.PrintStream getOutput()
	{
		return thePS;
	}

	/**
	 * @param s The text to print in the status bar
	 */
	protected void setStatus(String s)
	{
		theStatusBar.setText(s);
		theStatusBar.repaint();
	}

	/**
	 * @param c The color to use for the text in the status bar
	 */
	protected void setStatusColor(Color c)
	{
		theStatusBar.setForeground(c);
	}

	/**
	 * @return The root file or directory to search
	 */
	public File getSearchFile()
	{
		return theFile;
	}

	boolean fileFieldWriteLock;

	void setSearchFileText(String f)
	{
		if(!fileFieldWriteLock)
		{
			fileFieldWriteLock = true;
			try
			{
				theSetup.theFileField.setText(theFile.getPath());
			} finally
			{
				fileFieldWriteLock = false;
			}
		}
	}

	/**
	 * @param f The root file or directory to search
	 */
	public void setSearchFile(String f)
	{
		if(f == null)
			throw new IllegalArgumentException("Null search file not allowed");
		theFile = new File(f);
		setSearchFileText(f);
		if(!theFile.exists())
		{
			theSetup.theFileField.setBackground(Color.RED);
			theSetup.theFileField.setToolTipText("File \"" + theFile.getPath()
				+ "\" does not exist");
		}
		else
		{
			theSetup.theFileField.setBackground(Color.WHITE);
			theSetup.theFileField.setToolTipText("Enter the root directory or" + " file to search");
		}
	}

	/**
	 * @return The SearchCriteria determining what is being searched for
	 */
	public SearchCriteria getInclude()
	{
		return theInclude;
	}

	/**
	 * @return The set of SearchCriteria determining what is excluded from the search
	 */
	public java.util.List<SearchCriteria> getExcludes()
	{
		return theExcludes;
	}

	/**
	 * @param exs The set of SearchCriteria to use
	 */
	public void setExcludes(SearchCriteria [] exs)
	{
		theExcludes.clear();
		for(int i = 0; i < exs.length; i++)
			theExcludes.add(exs[i]);
	}

	/**
	 * @return The Searcher engine that will perform the search
	 */
	public Searcher getSearcher()
	{
		return theSearcher;
	}

	/**
	 * @return Whether all text matches are printed to output or just the total number of matches
	 */
	public boolean isPrintAllTextMatches()
	{
		return printAllTextMatches;
	}

	/**
	 * @param patm Whether all text matches are to be printed to output or just the total number of
	 *        matches
	 */
	public void setPrintAllTextMatches(boolean patm)
	{
		printAllTextMatches = patm;
	}

	/**
	 * @param zl The maximum depth that a search will descend into zip files
	 */
	public void setZipLevel(int zl)
	{
		theSearcher.setZipLevel(zl);
		if(((Number) theSetup.theZipSpinner.getValue()).intValue() != zl)
			theSetup.theZipSpinner.setValue(new Integer(zl));
	}

	/**
	 * @param access The rule that determines whether a file's read access matches the search
	 *        criteria
	 * @see SearchCriteria#REQUIRED
	 * @see SearchCriteria#ALLOWED
	 * @see SearchCriteria#DISALLOWED
	 */
	public void setReadAccess(int access)
	{
		if(access == SearchCriteria.REQUIRED)
			theSetup.theReadMust.setSelected(true);
		else if(access == SearchCriteria.ALLOWED)
			theSetup.theReadMay.setSelected(true);
		else if(access == SearchCriteria.DISALLOWED)
			theSetup.theReadCant.setSelected(true);
		getInclude().setReadAccess(access);
	}

	/**
	 * @param access The rule that determines whether a file's write access matches the search
	 *        criteria
	 * @see SearchCriteria#REQUIRED
	 * @see SearchCriteria#ALLOWED
	 * @see SearchCriteria#DISALLOWED
	 */
	public void setWriteAccess(int access)
	{
		if(access == SearchCriteria.REQUIRED)
			theSetup.theWriteMust.setSelected(true);
		else if(access == SearchCriteria.ALLOWED)
			theSetup.theWriteMay.setSelected(true);
		else if(access == SearchCriteria.DISALLOWED)
			theSetup.theWriteCant.setSelected(true);
		getInclude().setWriteAccess(access);
	}

	/**
	 * @param access The rule that determines whether a file's directory attribute matches the
	 *        search criteria
	 * @see SearchCriteria#REQUIRED
	 * @see SearchCriteria#ALLOWED
	 * @see SearchCriteria#DISALLOWED
	 */
	public void setDirAccess(int access)
	{
		if(access == SearchCriteria.REQUIRED)
			theSetup.theDirMust.setSelected(true);
		else if(access == SearchCriteria.ALLOWED)
			theSetup.theDirMay.setSelected(true);
		else if(access == SearchCriteria.DISALLOWED)
			theSetup.theDirCant.setSelected(true);
		getInclude().setDirectoryAccess(access);
	}

	/**
	 * @param access The rule that determines whether a file's hidden attribute matches the search
	 *        criteria
	 * @see SearchCriteria#REQUIRED
	 * @see SearchCriteria#ALLOWED
	 * @see SearchCriteria#DISALLOWED
	 */
	public void setHiddenAccess(int access)
	{
		if(access == SearchCriteria.REQUIRED)
			theSetup.theHiddenMust.setSelected(true);
		else if(access == SearchCriteria.ALLOWED)
			theSetup.theHiddenMay.setSelected(true);
		else if(access == SearchCriteria.DISALLOWED)
			theSetup.theHiddenCant.setSelected(true);
		getInclude().setHiddenAccess(access);
	}

	/**
	 * @param time The minimum modification time a file can have to meet the search criteria
	 */
	public void setMinModTime(long time)
	{
		if(((Date) theSetup.theMinModTime.getValue()).getTime() != time)
			theSetup.theMinModTime.setValue(new Date(time));
		theInclude.setMinModTime(time);
	}

	/**
	 * @param time The maximum modification time a file can have to meet the search criteria
	 */
	public void setMaxModTime(long time)
	{
		if(((Date) theSetup.theMaxModTime.getValue()).getTime() != time)
			theSetup.theMaxModTime.setValue(new Date(time));
		theInclude.setMaxModTime(time);
	}

	/**
	 * @param sz The minimum size a file can have to meet the search criteria
	 */
	public void setMinSize(long sz)
	{
		if(((Number) theSetup.theMinSize.getValue()).longValue() != sz)
			theSetup.theMinSize.setValue(new Long(sz));
		theInclude.setMinSize(sz);
	}

	/**
	 * @param sz The maximum size a file can have to meet the search criteria
	 */
	public void setMaxSize(long sz)
	{
		if(((Number) theSetup.theMaxSize.getValue()).longValue() != sz)
			theSetup.theMaxSize.setValue(new Long(sz));
		theInclude.setMaxSize(sz);
	}

	/**
	 * Starts the search
	 */
	public void doSearch()
	{
		if(doStartSearch)
			throw new IllegalStateException("Cannot start another search"
				+ " while one is in progress");
		doStartSearch = true;
	}

	/**
	 * Terminates a search
	 */
	public void stopSearch()
	{
		if(!doStartSearch)
			throw new IllegalStateException("Cannot stop search: no search is" + " in progress");
		theInclude.stop();
		theSearcher.stop();
	}

	/**
	 * Pauses the search
	 */
	public void pauseSearch()
	{
		theSearcher.pause();
	}

	/**
	 * Resumes a search that has been paused
	 */
	public void unpauseSearch()
	{
		theSearcher.unpause();
	}

	public void run()
	{
		while(true)
		{
			if(doStartSearch)
			{
				File searchFile = getSearchFile();
				FileMatch [] matches = new FileMatch [0];
				try
				{
					theOutput.setText("");
					theSearchButton.setEnabled(false);
					theStopButton.setEnabled(true);
					thePauseButton.setEnabled(true);
					matches = theSearcher.search(searchFile, getInclude(), getExcludes(), false);
				} catch(StopException e)
				{
					getOutput().println("User stopped search");
					matches = e.getFileMatches();
					e.printStackTrace(System.out);
				} catch(RuntimeException e)
				{
					e.printStackTrace();
				} catch(Error e)
				{
					e.printStackTrace();
				} finally
				{
					doStartSearch = false;
					theSearchButton.setEnabled(true);
					theStopButton.setEnabled(false);
					thePauseButton.setEnabled(false);
					thePauseButton.setText("Pause Search");
					theSearcher.unstop();
					theSearcher.unpause();
					theInclude.unstop();
					String status = matches.length + " file match";
					if(matches.length != 1)
						status += "es";
					int txtMatches = getTextMatchCount(matches);
					if(txtMatches > 0)
					{
						status += " (" + txtMatches + " text match";
						if(txtMatches > 1)
							status += "es";
						status += ")";
					}
					status += " found";
					setStatus(status);
					setStatusColor(Color.BLACK);
					try
					{
						theOutputTree.setModel(new FileTreeModel(searchFile,
							new SearchResultsFileFilter(matches)));
					} catch(Exception e)
					{
						e.printStackTrace();
					}
				}
			}
			try
			{
				Thread.sleep(100);
			} catch(InterruptedException e)
			{
				System.err.println("Unexpected interrupt");
				e.printStackTrace();
			}
		}
	}

	private int getTextMatchCount(FileMatch [] matches)
	{
		int ret = 0;
		for(FileMatch fm : matches)
			if(fm.getTextMatches() != null)
				ret += fm.getTextMatches().length;
		return ret;
	}

	private final class Setup
	{
		JTextField theFileField;

		JTextField thePathField;

		JCheckBox thePathCIBox;

		JCheckBox thePathUseBox;

		JTextField thePathTester;

		JTextField theTextPatternField;

		JCheckBox theTextCIBox;

		JCheckBox theTextWordBox;

		JCheckBox theTextUseBox;

		JTextField theTextTester;

		JLabel theTextTestResult;

		JCheckBox theTextSearchMultiple;

		JCheckBox theTextPrintAll;

		JSpinner theZipSpinner;

		JRadioButton theReadMust;

		JRadioButton theReadMay;

		JRadioButton theReadCant;

		JRadioButton theWriteMust;

		JRadioButton theWriteMay;

		JRadioButton theWriteCant;

		JRadioButton theDirMust;

		JRadioButton theDirMay;

		JRadioButton theDirCant;

		JRadioButton theHiddenMust;

		JRadioButton theHiddenMay;

		JRadioButton theHiddenCant;

		JSpinner theMinModTime;

		JSpinner theMaxModTime;

		JSpinner theMinSize;

		JSpinner theMaxSize;

		JPanel theExclusionPanel;

		/**
		 * Creates a setup for this SearcherGui
		 */
		public Setup()
		{
		}

		void setupMenuBar()
		{
			javax.swing.JMenuBar mb = new javax.swing.JMenuBar();
			setJMenuBar(mb);
			javax.swing.JMenu fileMenu = new javax.swing.JMenu("File");
			fileMenu.setMnemonic('f');
			mb.add(fileMenu);
			JMenuItem saveItem = new JMenuItem("Save Search...");
			fileMenu.add(saveItem);
			saveItem.setMnemonic('s');
			saveItem.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					String toSave = userSelectFile(".", false, false);
					if(toSave == null)
						return;
					try
					{
						saveState(toSave);
					} catch(IOException e)
					{
						e.printStackTrace();
						JOptionPane.showMessageDialog(SearcherGui.this, "Could not save search: "
							+ e.getMessage(), "Save Failed", JOptionPane.ERROR_MESSAGE);
					}
				}
			});
			JMenuItem openItem = new JMenuItem("Open Search...");
			fileMenu.add(openItem);
			openItem.setMnemonic('o');
			openItem.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					String toLoad = userSelectFile(".", true, false);
					if(toLoad == null)
						return;
					try
					{
						loadState(toLoad);
					} catch(Exception e)
					{
						e.printStackTrace();
						JOptionPane.showMessageDialog(SearcherGui.this, "Could not open search",
							"Save Failed", JOptionPane.ERROR_MESSAGE);
					}
				}
			});
			JMenuItem exitItem = new JMenuItem("Exit");
			fileMenu.add(exitItem);
			exitItem.setMnemonic('x');
			exitItem.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					try
					{
						saveState("defaultSearch.xml");
					} catch(IOException e)
					{
						System.err.println("Could not save search state");
						e.printStackTrace();
					}
					saveSize();
					System.exit(0);
				}
			});
		}

		javax.swing.JComponent createSettingsPanel()
		{
			JPanel ret = new JPanel();
			ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
			ret.add(createSearchDirPanel());
			ret.add(createPathSearchPanel());
			ret.add(createTextSearchPanel());
			ret.add(createMiscPanel());
			ret.add(createExclusionPanel());
			return new JScrollPane(ret);
		}

		private javax.swing.JComponent createSearchDirPanel()
		{
			JPanel ret = new JPanel();
			ret.setBorder(new LineBorder(Color.BLACK, 1));
			ret.setPreferredSize(new Dimension(350, 45));
			ret.setMaximumSize(new Dimension(1000000, 45));
			ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
			ret.add(new JLabel("Search Directory"));
			JPanel browsePanel = new JPanel();
			ret.add(browsePanel);
			browsePanel.setLayout(new BorderLayout());
			theFileField = new JTextField();
			browsePanel.add(theFileField, BorderLayout.WEST);
			theFileField.setColumns(35);
			theFileField.setText(getSearchFile().getAbsolutePath());
			theFileField.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt)
				{
					fileFieldWriteLock = true;
					try
					{
						setSearchFile(theFileField.getText());
					} finally
					{
						fileFieldWriteLock = false;
					}
				}
			});
			JButton browseButton = new JButton("Browse...");
			browsePanel.add(browseButton, BorderLayout.EAST);
			browseButton.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					String newFile = userSelectFile(getSearchFile().getPath(), true, true);
					if(newFile != null)
						setSearchFile(newFile);
				}
			});
			return ret;
		}

		private javax.swing.JComponent createPathSearchPanel()
		{
			JPanel ret = new JPanel();
			ret.setBorder(new LineBorder(Color.BLACK, 1));
			ret.setPreferredSize(new Dimension(350, 100));
			ret.setMaximumSize(new Dimension(1000000, 100));
			ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
			JPanel topPanel = new JPanel();
			ret.add(topPanel);
			topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
			JPanel patPanel = new JPanel();
			topPanel.add(patPanel);
			patPanel.setLayout(new GridLayout(2, 1));
			patPanel.add(new JLabel("File Pattern"));
			thePathField = new JTextField();
			patPanel.add(thePathField);
			thePathField.setColumns(30);
			thePathField.setEnabled(false);
			thePathField.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt)
				{
					setPathPattern(thePathField.getText(), !thePathCIBox.isSelected());
				}
			});
			JPanel ciPanel = new JPanel();
			topPanel.add(ciPanel);
			ciPanel.setLayout(new GridLayout(2, 1));
			ciPanel.add(new JLabel("Case"));
			thePathCIBox = new JCheckBox();
			ciPanel.add(thePathCIBox);
			thePathCIBox.setSelected(false);
			thePathCIBox.setEnabled(false);
			thePathCIBox.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setPathPattern(thePathField.getText(), !thePathCIBox.isSelected());
				}
			});
			JPanel usePanel = new JPanel();
			topPanel.add(usePanel);
			usePanel.setLayout(new GridLayout(2, 1));
			usePanel.add(new JLabel("Use"));
			thePathUseBox = new JCheckBox();
			usePanel.add(thePathUseBox);
			thePathUseBox.setSelected(false);
			thePathUseBox.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setPathPatternEnabled(thePathUseBox.isSelected());
				}
			});
			JPanel testPanel = new JPanel();
			ret.add(testPanel);
			testPanel.setLayout(new GridLayout(2, 1));
			testPanel.add(new JLabel("Test:  "));
			thePathTester = new JTextField();
			testPanel.add(thePathTester);
			thePathTester.setColumns(30);
			thePathTester.setEnabled(false);
			thePathTester.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt)
				{
					doPathTest();
				}
			});
			return ret;
		}

		void setPathPatternEnabled(boolean b)
		{
			if(thePathUseBox.isSelected() != b)
				thePathUseBox.setSelected(b);
			thePathField.setEnabled(b);
			thePathCIBox.setEnabled(b);
			thePathTester.setEnabled(b);
			if(b)
				getInclude().setPathPattern(thePathField.getText(), !thePathCIBox.isSelected());
			else
				getInclude().setPathPattern(null);
		}

		void setPathPattern(String patt, boolean ci)
		{
			if(!patt.equals(thePathField.getText()))
				thePathField.setText(patt);
			if(thePathCIBox.isSelected() == ci)
				thePathCIBox.setSelected(!ci);
			try
			{
				getInclude().setPathPattern(patt, ci);
				thePathField.setBackground(Color.WHITE);
				thePathField.setToolTipText("Enter the pattern of the file"
					+ " name to search for ");
				doPathTest();
			} catch(Exception e)
			{
				thePathField.setBackground(Color.RED);
				thePathField.setToolTipText("Unrecognized pattern");
			}
		}

		void doPathTest()
		{
			if(getInclude().getPathPattern() == null || thePathTester.getText().length() == 0)
				thePathTester.setBackground(Color.WHITE);
			else if(getInclude().getPathPattern().matcher(thePathTester.getText()).matches())
				thePathTester.setBackground(Color.WHITE);
			else
				thePathTester.setBackground(Color.RED);
		}

		private javax.swing.JComponent createTextSearchPanel()
		{
			JPanel ret = new JPanel();
			ret.setBorder(new LineBorder(Color.BLACK, 1));
			ret.setPreferredSize(new Dimension(350, 140));
			ret.setMaximumSize(new Dimension(1000000, 140));
			ret.setLayout(new BoxLayout(ret, BoxLayout.Y_AXIS));
			JPanel topPanel = new JPanel();
			ret.add(topPanel);
			topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
			JPanel patPanel = new JPanel();
			topPanel.add(patPanel);
			patPanel.setLayout(new GridLayout(2, 1));
			patPanel.add(new JLabel("Text Pattern"));
			theTextPatternField = new JTextField();
			patPanel.add(theTextPatternField);
			theTextPatternField.setColumns(30);
			theTextPatternField.setEnabled(false);
			theTextPatternField.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt)
				{
					setTextPattern(theTextPatternField.getText(), !theTextCIBox.isSelected());
				}
			});
			JPanel ciPanel = new JPanel();
			topPanel.add(ciPanel);
			ciPanel.setLayout(new GridLayout(2, 1));
			ciPanel.add(new JLabel("Case"));
			theTextCIBox = new JCheckBox();
			ciPanel.add(theTextCIBox);
			theTextCIBox.setSelected(false);
			theTextCIBox.setEnabled(false);
			theTextCIBox.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setTextPattern(theTextPatternField.getText(), !theTextCIBox.isSelected());
				}
			});
			JPanel wordPanel = new JPanel();
			topPanel.add(wordPanel);
			wordPanel.setLayout(new GridLayout(2, 1));
			wordPanel.add(new JLabel("Whole Words"));
			theTextWordBox = new JCheckBox();
			wordPanel.add(theTextWordBox);
			theTextWordBox.setSelected(getInclude().isTextSearchByWord());
			theTextWordBox.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setTextWholeWords(theTextWordBox.isSelected());
				}
			});
			JPanel usePanel = new JPanel();
			topPanel.add(usePanel);
			usePanel.setLayout(new GridLayout(2, 1));
			usePanel.add(new JLabel("Use"));
			theTextUseBox = new JCheckBox();
			usePanel.add(theTextUseBox);
			theTextUseBox.setSelected(false);
			theTextUseBox.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setTextPatternEnabled(theTextUseBox.isSelected());
				}
			});
			JPanel testPanel = new JPanel();
			ret.add(testPanel);
			testPanel.setLayout(new GridLayout(2, 2));
			testPanel.add(new JLabel("Test:  "));
			testPanel.add(new JPanel());
			theTextTester = new JTextField();
			testPanel.add(theTextTester);
			theTextTester.setColumns(30);
			theTextTester.setEnabled(false);
			theTextTester.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt)
				{
					doTextTest();
				}
			});
			theTextTestResult = new JLabel();
			testPanel.add(theTextTestResult);
			JPanel formatPanel = new JPanel();
			ret.add(formatPanel);
			formatPanel.setLayout(new BoxLayout(formatPanel, BoxLayout.Y_AXIS));
			JPanel smPanel = new JPanel();
			formatPanel.add(smPanel);
			smPanel.setLayout(new BorderLayout());
			smPanel.add(new JLabel("Search Multiple Matches:  "), BorderLayout.CENTER);
			theTextSearchMultiple = new JCheckBox();
			smPanel.add(theTextSearchMultiple, BorderLayout.EAST);
			theTextSearchMultiple.setEnabled(false);
			theTextSearchMultiple.setSelected(getInclude().getTextSearchMultiple());
			theTextSearchMultiple.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setTextSearchMultiple(theTextSearchMultiple.isSelected());
				}
			});
			JPanel paPanel = new JPanel();
			formatPanel.add(paPanel);
			paPanel.setLayout(new BorderLayout());
			paPanel.add(new JLabel("Print All Matches:  "), BorderLayout.CENTER);
			theTextPrintAll = new JCheckBox();
			paPanel.add(theTextPrintAll, BorderLayout.EAST);
			theTextPrintAll.setEnabled(false);
			theTextPrintAll.setSelected(isPrintAllTextMatches());
			theTextPrintAll.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setTextPrintAll(theTextPrintAll.isSelected());
				}
			});
			return ret;
		}

		void setTextPatternEnabled(boolean b)
		{
			if(theTextUseBox.isSelected() != b)
				theTextUseBox.setSelected(b);
			theTextPatternField.setEnabled(b);
			theTextCIBox.setEnabled(b);
			theTextWordBox.setEnabled(b);
			theTextTester.setEnabled(b);
			theTextSearchMultiple.setEnabled(b);
			if(b)
			{
				getInclude().setTextPattern(theTextPatternField.getText(),
					!thePathCIBox.isSelected());
				theTextPrintAll.setEnabled(theTextSearchMultiple.isSelected());
			}
			else
			{
				getInclude().setTextPattern(null);
				theTextPrintAll.setEnabled(false);
			}
		}

		void setTextPattern(String patt, boolean ci)
		{
			if(!patt.equals(theTextPatternField.getText()))
				theTextPatternField.setText(patt);
			if(theTextCIBox.isSelected() == ci)
				theTextCIBox.setSelected(!ci);
			try
			{
				getInclude().setTextPattern(patt, ci);
				theTextPatternField.setBackground(Color.WHITE);
				theTextPatternField.setToolTipText("Enter the pattern to"
					+ " search file contents for ");
				doTextTest();
			} catch(Exception e)
			{
				theTextPatternField.setBackground(Color.RED);
				theTextPatternField.setToolTipText("Unrecognized pattern");
			}
		}

		void setTextWholeWords(boolean b)
		{
			if(b != theTextWordBox.isSelected())
				theTextWordBox.setSelected(b);
			getInclude().setTextSearchByWord(b);
		}

		void setTextSearchMultiple(boolean b)
		{
			if(b != theTextSearchMultiple.isSelected())
				theTextSearchMultiple.setSelected(b);
			getInclude().setTextSearchMultiple(b);
			theTextPrintAll.setEnabled(theTextUseBox.isSelected()
				&& theTextSearchMultiple.isSelected());
		}

		void setTextPrintAll(boolean b)
		{
			System.out.println("Setting text print all to " + b);
			if(b != theTextPrintAll.isSelected())
				theTextPrintAll.setSelected(b);
			setPrintAllTextMatches(b);
		}

		void doTextTest()
		{
			if(getInclude().getTextPattern() == null || theTextTester.getText().length() == 0)
				theTextTestResult.setText("");
			else
			{
				int matches = 0;
				java.util.regex.Matcher m = getInclude().getTextPattern().matcher(
					theTextTester.getText());
				while(m.find())
					matches++;
				String res = "" + matches;
				if(matches == 1)
					res += " match";
				else
					res += " matches";
				theTextTestResult.setText(res);
			}
		}

		javax.swing.JComponent createMiscPanel()
		{
			JPanel ret = new JPanel();
			ret.setPreferredSize(new Dimension(350, 200));
			ret.setMaximumSize(new Dimension(100000, 200));
			ret.setLayout(null);
			ret.setBorder(new LineBorder(Color.BLACK, 1));
			JPanel zipPanel = new JPanel();
			ret.add(zipPanel);
			zipPanel.setLocation(0, 0);
			zipPanel.setSize(350, 30);
			zipPanel.setLayout(new BorderLayout());
			zipPanel.setPreferredSize(new Dimension(350, 40));
			zipPanel.setMaximumSize(new Dimension(350, 40));
			zipPanel.add(new JLabel("Zip Level"), BorderLayout.WEST);
			getSearcher().setZipLevel(10);
			theZipSpinner = new JSpinner(new SpinnerNumberModel(10, 0, 100, 1));
			zipPanel.add(theZipSpinner, BorderLayout.EAST);
			theZipSpinner.setPreferredSize(new Dimension(100, 30));
			theZipSpinner.setMaximumSize(new Dimension(70, 30));
			theZipSpinner.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent evt)
				{
					int value = ((Number) theZipSpinner.getValue()).intValue();
					setZipLevel(value);
				}
			});
			theReadMust = new JRadioButton();
			theReadMust.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setReadAccess(SearchCriteria.REQUIRED);
				}
			});
			theReadMay = new JRadioButton();
			theReadMay.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setReadAccess(SearchCriteria.ALLOWED);
				}
			});
			theReadCant = new JRadioButton();
			theReadCant.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setReadAccess(SearchCriteria.DISALLOWED);
				}
			});
			theWriteMust = new JRadioButton();
			theWriteMust.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setWriteAccess(SearchCriteria.REQUIRED);
				}
			});
			theWriteMay = new JRadioButton();
			theWriteMay.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setWriteAccess(SearchCriteria.ALLOWED);
				}
			});
			theWriteCant = new JRadioButton();
			theWriteCant.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setWriteAccess(SearchCriteria.DISALLOWED);
				}
			});
			theDirMust = new JRadioButton();
			theDirMust.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setDirAccess(SearchCriteria.REQUIRED);
				}
			});
			theDirMay = new JRadioButton();
			theDirMay.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setDirAccess(SearchCriteria.ALLOWED);
				}
			});
			theDirCant = new JRadioButton();
			theDirCant.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setDirAccess(SearchCriteria.DISALLOWED);
				}
			});
			theHiddenMust = new JRadioButton();
			theHiddenMust.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setHiddenAccess(SearchCriteria.REQUIRED);
				}
			});
			theHiddenMay = new JRadioButton();
			theHiddenMay.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setHiddenAccess(SearchCriteria.ALLOWED);
				}
			});
			theHiddenCant = new JRadioButton();
			theHiddenCant.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					setHiddenAccess(SearchCriteria.DISALLOWED);
				}
			});
			ButtonGroup readGroup = new ButtonGroup();
			ButtonGroup writeGroup = new ButtonGroup();
			ButtonGroup dirGroup = new ButtonGroup();
			ButtonGroup hiddenGroup = new ButtonGroup();
			readGroup.add(theReadMust);
			readGroup.add(theReadMay);
			readGroup.add(theReadCant);
			writeGroup.add(theWriteMust);
			writeGroup.add(theWriteMay);
			writeGroup.add(theWriteCant);
			dirGroup.add(theDirMust);
			dirGroup.add(theDirMay);
			dirGroup.add(theDirCant);
			hiddenGroup.add(theHiddenMust);
			hiddenGroup.add(theHiddenMay);
			hiddenGroup.add(theHiddenCant);
			JPanel accessPanel = new JPanel();
			ret.add(accessPanel);
			accessPanel.setLocation(0, 30);
			accessPanel.setSize(300, 100);
			accessPanel.setMaximumSize(new Dimension(300, 100));
			accessPanel.setLayout(new GridLayout(4, 5));
			accessPanel.add(new JLabel("Access:"));
			accessPanel.add(new JLabel("Readable"));
			accessPanel.add(new JLabel("Writable"));
			accessPanel.add(new JLabel("Directory"));
			accessPanel.add(new JLabel("Hidden"));
			accessPanel.add(new JLabel("Required:"));
			accessPanel.add(theReadMust);
			accessPanel.add(theWriteMust);
			accessPanel.add(theDirMust);
			accessPanel.add(theHiddenMust);
			accessPanel.add(new JLabel("Allowed:"));
			accessPanel.add(theReadMay);
			accessPanel.add(theWriteMay);
			accessPanel.add(theDirMay);
			accessPanel.add(theHiddenMay);
			accessPanel.add(new JLabel("Disallowed:"));
			accessPanel.add(theReadCant);
			accessPanel.add(theWriteCant);
			accessPanel.add(theDirCant);
			accessPanel.add(theHiddenCant);
			setReadAccess(getInclude().getReadAccess());
			setWriteAccess(getInclude().getWriteAccess());
			setDirAccess(getInclude().getDirectoryAccess());
			setHiddenAccess(getInclude().getHiddenAccess());

			JPanel modSizePanel = new JPanel();
			ret.add(modSizePanel);
			modSizePanel.setLocation(0, 130);
			modSizePanel.setSize(350, 60);
			modSizePanel.setMaximumSize(new Dimension(350, 60));
			modSizePanel.setLayout(new GridLayout(2, 3));
			theMinModTime = new JSpinner(new javax.swing.SpinnerDateModel(new Date(getInclude()
				.getMinModTime()), new Date(0), null, java.util.Calendar.MINUTE));
			theMinModTime.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent evt)
				{
					setMinModTime(((Date) ((JSpinner) evt.getSource()).getValue()).getTime());
				}
			});
			theMinModTime.setMaximumSize(new Dimension(120, 30));
			theMinModTime.setPreferredSize(new Dimension(120, 30));
			modSizePanel.add(theMinModTime);
			modSizePanel.add(new JLabel(" <= Last Modified <= "));
			theMaxModTime = new JSpinner(new javax.swing.SpinnerDateModel(new Date(getInclude()
				.getMaxModTime()), new Date(0), null, java.util.Calendar.MINUTE));
			theMaxModTime.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent evt)
				{
					setMaxModTime(((Date) ((JSpinner) evt.getSource()).getValue()).getTime());
				}
			});
			theMaxModTime.setMaximumSize(new Dimension(120, 30));
			theMaxModTime.setPreferredSize(new Dimension(120, 30));
			modSizePanel.add(theMaxModTime);

			theMinSize = new JSpinner(new javax.swing.SpinnerNumberModel(new Long(getInclude()
				.getMinSize()), new Long(0), null, new Long(1024)));
			theMinSize.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent evt)
				{
					setMinSize(((Number) ((JSpinner) evt.getSource()).getValue()).longValue());
				}
			});
			theMinSize.setMaximumSize(new Dimension(120, 30));
			theMinSize.setPreferredSize(new Dimension(120, 30));
			modSizePanel.add(theMinSize);
			modSizePanel.add(new JLabel(" <= Size <= "));
			theMaxSize = new JSpinner(new javax.swing.SpinnerNumberModel(new Long(getInclude()
				.getMaxSize()), new Long(0), null, new Long(1024)));
			theMaxSize.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent evt)
				{
					setMaxSize(((Number) ((JSpinner) evt.getSource()).getValue()).longValue());
				}
			});
			theMaxSize.setMaximumSize(new Dimension(120, 30));
			theMaxSize.setPreferredSize(new Dimension(120, 30));
			modSizePanel.add(theMaxSize);
			return ret;
		}

		javax.swing.JComponent createExclusionPanel()
		{
			theExclusionPanel = new JPanel();
			theExclusionPanel.setBorder(new LineBorder(Color.black, 1));
			theExclusionPanel.setLayout(new BoxLayout(theExclusionPanel, BoxLayout.Y_AXIS));
			theExclusionPanel.add(new JLabel("File Exclusion Patterns"));
			JButton addExButton = new JButton("Add");
			theExclusionPanel.add(addExButton);
			addExButton.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt)
				{
					final SearchCriteria newEx = new SearchCriteria("", true, null, true);
					newEx.addSearchListener(theExcludeListener);
					getExcludes().add(newEx);
					theExclusionPanel.add(createExclusionPanelFor(newEx));
					theExclusionPanel.getParent().validate();
					theExclusionPanel.repaint();
				}
			});
			return new javax.swing.JScrollPane(theExclusionPanel);
		}

		javax.swing.JComponent createExclusionPanelFor(final SearchCriteria sc)
		{
			final JPanel ret = new JPanel();
			ret.setMaximumSize(new Dimension(1000000, 60));
			ret.setBorder(new LineBorder(Color.black, 1));
			ret.setLayout(new BoxLayout(ret, BoxLayout.X_AXIS));
			JPanel patPanel = new JPanel(new GridLayout(2, 1));
			ret.add(patPanel);
			patPanel.add(new JLabel("Pattern"));
			final JTextField exField = new JTextField();
			patPanel.add(exField);
			exField.setText(sc.getPathPatternString());
			JPanel ciPanel = new JPanel(new GridLayout(2, 1));
			ret.add(ciPanel);
			ciPanel.add(new JLabel("Case"));
			final JCheckBox exCI = new JCheckBox();
			ciPanel.add(exCI);
			int ci = sc.getPathPattern().flags() | java.util.regex.Pattern.CASE_INSENSITIVE;
			exCI.setSelected(ci == 0);
			JButton remBtn = new JButton("Remove");
			ret.add(remBtn);
			exField.getDocument().addDocumentListener(new TextListener()
			{
				public void textChanged(DocumentEvent evt2)
				{
					try
					{
						sc.setPathPattern(exField.getText(), !exCI.isSelected());
						exField.setBackground(Color.white);
						exField.setToolTipText("Enter the pattern to exclude");
					} catch(Exception e)
					{
						exField.setBackground(Color.red);
						exField.setToolTipText("Invalid pattern");
					}
				}
			});
			exCI.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt2)
				{
					try
					{
						sc.setPathPattern(exField.getText(), !exCI.isSelected());
						exField.setBackground(Color.white);
						exField.setToolTipText("Enter the pattern to exclude");
					} catch(Exception e)
					{
						exField.setBackground(Color.red);
						exField.setToolTipText("Invalid pattern");
					}
				}
			});
			remBtn.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt2)
				{
					theExclusionPanel.remove(ret);
					theExclusionPanel.getParent().validate();
					theExclusionPanel.repaint();
					int i;
					java.util.List<SearchCriteria> oldExs = getExcludes();
					i = oldExs.indexOf(sc);
					if(i < 0)
					{
						System.err.println("Pattern not found in"
							+ " exclusion patterns--cannot remove");
						return;
					}
					oldExs.remove(i);
				}
			});
			return ret;
		}

		void setExclusions(SearchCriteria [] scs)
		{
			while(theExclusionPanel.getComponentCount() > 2)
				theExclusionPanel.remove(theExclusionPanel.getComponentCount() - 1);
			for(int i = 0; i < scs.length; i++)
				theExclusionPanel.add(createExclusionPanelFor(scs[i]));
			theExclusionPanel.getParent().validate();
			theExclusionPanel.repaint();
		}

		/**
		 * Pops up a FileDialog to allow the user to select a file to open or save to
		 * 
		 * @param dir The directory under the IWEDA user directory to look in
		 * @param withDirs Whether the dialog should allow the user to select directories
		 * @return The file name that the user selected, or null if the action was cancelled
		 */
		String userSelectFile(String dir, boolean load, boolean withDirs)
		{
			JFileChooser fc = new JFileChooser();
			java.io.File dirFile = new java.io.File(dir);
			fc.setCurrentDirectory(dirFile);
			int res;
			if(withDirs)
				fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			if(load)
				res = fc.showOpenDialog(SearcherGui.this);
			else
				res = fc.showSaveDialog(SearcherGui.this);
			if(res == JFileChooser.APPROVE_OPTION)
				return fc.getSelectedFile().getAbsolutePath();
			else
				return null;
		}

		private abstract class TextListener implements javax.swing.event.DocumentListener
		{
			TextListener()
			{
			}

			/**
			 * Called from all other methods in {@link TextListener}
			 * 
			 * @param e The DocumentEvent containing the changes made to the document
			 */
			public abstract void textChanged(DocumentEvent e);

			public void changedUpdate(DocumentEvent e)
			{
				textChanged(e);
			}

			public void insertUpdate(DocumentEvent e)
			{
				textChanged(e);
			}

			public void removeUpdate(DocumentEvent e)
			{
				textChanged(e);
			}
		}
	}

	class IncludeSearchListener implements SearchListener
	{
		public void fileSearched(SearchCriteria sc, File aFile)
		{
			setStatus(aFile.getPath());
			if(aFile.getEnclArchive() != null)
				setStatusColor(Color.BLUE);
			else
				setStatusColor(Color.BLACK);
		}

		public void fileMatch(SearchCriteria sc, FileMatch match)
		{
			String toPrint = match.getFile();
			if(match.getTextMatches() != null && match.getTextMatches().length > 0)
			{
				toPrint += "(" + match.getTextMatches().length + ")";
				TextMatch tm;
				if(isPrintAllTextMatches())
				{
					for(int i = 0; i < match.getTextMatches().length; i++)
					{
						tm = match.getTextMatches()[i];
						toPrint += "\n\t" + tm.getLineNumber() + " : " + tm.getCharNumber();
					}
				}
			}
			getOutput().println(toPrint);
		}

		public void filePathMatch(SearchCriteria sc, File aFile)
		{
		}

		public void fileTextMatch(SearchCriteria sc, File aFile, TextMatch match)
		{
		}
	}

	class ExcludeSearchListener implements SearchListener
	{
		public void fileMatch(SearchCriteria sc, FileMatch match)
		{
			getOutput().println("Excluded " + match.getFile());
		}

		public void filePathMatch(SearchCriteria sc, File aFile)
		{
		}

		public void fileSearched(SearchCriteria sc, File aFile)
		{
		}

		public void fileTextMatch(SearchCriteria sc, File aFile, TextMatch match)
		{
		}
	}

	class TextAreaWriter implements Runnable
	{
		private java.io.InputStream theInput;

		TextAreaWriter(java.io.InputStream is)
		{
			theInput = is;
		}

		public void run()
		{
			byte [] buffer = new byte [1024];
			while(true)
			{
				try
				{
					int amtRead = theInput.read(buffer);
					if(amtRead > 0)
					{
						theOutput.append(new String(buffer, 0, amtRead));
						int docLen = theOutput.getDocument().getLength();
						theOutput.setCaretPosition(docLen);
					}
				} catch(java.io.IOException e)
				{
					System.err.println("Unexpected I/O Exception");
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Saves the elements of a search to a file
	 * 
	 * @param aFileName The name of the file to save to
	 * @throws java.io.IOException If an error occurs writing the file
	 */
	public void saveState(String aFileName) throws IOException
	{
		java.io.OutputStream os = new java.io.BufferedOutputStream(new java.io.FileOutputStream(
			aFileName));
		try
		{
			org.dom4j.io.XMLWriter xmlWriter;
			xmlWriter = new org.dom4j.io.XMLWriter(os, new org.dom4j.io.OutputFormat("\t", true));
			xmlWriter.write(org.dom4j.DocumentFactory.getInstance().createDocument(getXMLState()));
		} finally
		{
			os.close();
		}
	}

	/**
	 * Loads the elements of a search from a file
	 * 
	 * @param aFileName The name of the file to load from
	 * @throws java.io.IOException If an error occurs reading the file
	 * @throws org.dom4j.DocumentException If the file is not XML
	 */
	public void loadState(String aFileName) throws IOException, org.dom4j.DocumentException
	{
		java.io.InputStream is = new java.io.BufferedInputStream(new java.io.FileInputStream(
			aFileName));
		org.dom4j.Document d;
		try
		{
			d = new org.dom4j.io.SAXReader().read(is);
		} finally
		{
			is.close();
		}
		setXMLState(d.getRootElement());
	}

	/**
	 * @return An element containing the elements of the current search
	 */
	public Element getXMLState()
	{
		Element ret = org.dom4j.DocumentFactory.getInstance().createElement("search");
		ret.addElement("searchdir").setText(theFile.getPath());
		Element include = ret.addElement("include");
		Element path = include.addElement("path");
		path.addAttribute("use", "" + theSetup.thePathUseBox.isSelected());
		path.addAttribute("case", "" + theSetup.thePathCIBox.isSelected());
		path.setText(theSetup.thePathField.getText());
		Element text = include.addElement("text");
		text.addAttribute("use", "" + theSetup.theTextUseBox.isSelected());
		text.addAttribute("case", "" + theSetup.theTextCIBox.isSelected());
		text.addAttribute("word", "" + theSetup.theTextWordBox.isSelected());
		text.addAttribute("multiple", "" + theSetup.theTextSearchMultiple.isSelected());
		text.addAttribute("printall", "" + theSetup.theTextPrintAll.isSelected());
		text.setText(theSetup.theTextPatternField.getText());
		Element excs = ret.addElement("excludes");
		SearchCriteria sc_i;
		for(int i = 0; i < theExcludes.size(); i++)
		{
			sc_i = theExcludes.get(i);
			Element exc_i = excs.addElement("exclude");
			int xci = sc_i.getPathPattern().flags() | java.util.regex.Pattern.CASE_INSENSITIVE;
			exc_i.addAttribute("case", "" + (xci != 0));
			exc_i.setText(sc_i.getPathPatternString());
		}
		ret.addElement("ziplevel").setText("" + theSearcher.getZipLevel());
		ret.addElement("readaccess").setText(accessString(theInclude.getReadAccess()));
		ret.addElement("writeaccess").setText(accessString(theInclude.getWriteAccess()));
		ret.addElement("hiddenaccess").setText(accessString(theInclude.getHiddenAccess()));
		ret.addElement("directoryaccess").setText(accessString(theInclude.getDirectoryAccess()));
		ret.addElement("minmodtime").setText(
			DATE_FORMAT.format(new java.util.Date(theInclude.getMinModTime())));
		ret.addElement("maxmodtime").setText(
			DATE_FORMAT.format(new java.util.Date(theInclude.getMaxModTime())));
		ret.addElement("minsize").setText("" + theInclude.getMinSize());
		ret.addElement("maxsize").setText("" + theInclude.getMaxSize());
		return ret;
	}

	private static String accessString(int access)
	{
		switch(access)
		{
		case SearchCriteria.REQUIRED:
			return "REQUIRED";
		case SearchCriteria.ALLOWED:
			return "ALLOWED";
		case SearchCriteria.DISALLOWED:
			return "DISALLOWED";
		default:
			return "UNRECOGNIZED";
		}
	}

	/**
	 * @param el Sets the elements of the current search from an XML element
	 */
	public void setXMLState(Element el)
	{
		if(!el.getName().equals("search"))
			throw new IllegalArgumentException("XML Element " + el.getName()
				+ " is not the root of a search state");
		setSearchFile(el.element("searchdir").getText());
		Element include = el.element("include");
		if(include == null)
			throw new IllegalArgumentException("include element is not present"
				+ "--not a valid search state");
		Element path = include.element("path");
		if(path == null)
			throw new IllegalArgumentException("path element is not present"
				+ "--not a valid search state");
		boolean use = "true".equalsIgnoreCase(path.attributeValue("use"));
		boolean cased = "true".equalsIgnoreCase(path.attributeValue("case"));
		theSetup.setPathPatternEnabled(use);
		theSetup.thePathField.setText(path.getText());
		theSetup.thePathCIBox.setSelected(cased);
		if(use)
			getInclude().setPathPattern(path.getText(), !cased);
		else
			getInclude().setPathPattern(null);
		Element text = include.element("text");
		if(text == null)
			throw new IllegalArgumentException("text element is not present"
				+ "--not a valid search state");
		use = "true".equalsIgnoreCase(text.attributeValue("use"));
		cased = "true".equalsIgnoreCase(text.attributeValue("case"));
		theSetup.setTextPatternEnabled(use);
		theSetup.theTextPatternField.setText(text.getText());
		theSetup.theTextCIBox.setSelected(cased);
		if(use)
			getInclude().setTextPattern(text.getText(), !cased);
		else
			getInclude().setTextPattern(null);
		theSetup.setTextWholeWords("true".equals(text.attributeValue("word")));
		theSetup.setTextSearchMultiple("true".equals(text.attributeValue("multiple")));
		theSetup.setTextPrintAll("true".equals(text.attributeValue("printall")));
		Element excs = el.element("excludes");
		if(excs == null)
			throw new IllegalArgumentException("excludes element is not"
				+ " present--not a valid search state");
		java.util.List<Element> exList = excs.elements();
		SearchCriteria [] newExs = new SearchCriteria [exList.size()];
		for(int i = 0; i < exList.size(); i++)
		{
			Element ex_i = exList.get(i);
			cased = "true".equalsIgnoreCase(ex_i.attributeValue("case"));
			newExs[i] = new SearchCriteria(ex_i.getText(), !cased, null, false);
			newExs[i].addSearchListener(theExcludeListener);
		}
		setExcludes(newExs);
		theSetup.setExclusions(newExs);
		Element temp = el.element("ziplevel");
		if(temp != null)
			setZipLevel(Integer.parseInt(temp.getText()));
		temp = el.element("readaccess");
		if(temp != null)
			setReadAccess(accessInt(temp.getText()));
		temp = el.element("writeaccess");
		if(temp != null)
			setWriteAccess(accessInt(temp.getText()));
		temp = el.element("directoryaccess");
		if(temp != null)
			setDirAccess(accessInt(temp.getText()));
		temp = el.element("hiddenaccess");
		if(temp != null)
			setHiddenAccess(accessInt(temp.getText()));
		temp = el.element("minmodtime");
		if(temp != null)
		{
			try
			{
				setMinModTime(DATE_FORMAT.parse(temp.getText()).getTime());
			} catch(java.text.ParseException e)
			{
				e.printStackTrace();
			}
		}
		temp = el.element("minsize");
		if(temp != null)
			setMinSize(Long.parseLong(temp.getText()));
		temp = el.element("maxsize");
		if(temp != null)
			setMaxSize(Long.parseLong(temp.getText()));
	}

	private static int accessInt(String access)
	{
		if("REQUIRED".equalsIgnoreCase(access))
			return SearchCriteria.REQUIRED;
		else if("ALLOWED".equalsIgnoreCase(access))
			return SearchCriteria.ALLOWED;
		else if("DISALLOWED".equalsIgnoreCase(access))
			return SearchCriteria.DISALLOWED;
		else
			return SearchCriteria.ALLOWED;
	}

	void saveSize()
	{
		Element rt = org.dom4j.DocumentFactory.getInstance().createElement("size");
		rt.addAttribute("x", "" + getX());
		rt.addAttribute("y", "" + getY());
		rt.addAttribute("width", "" + getWidth());
		rt.addAttribute("height", "" + getHeight());

		try
		{
			org.dom4j.Document doc = org.dom4j.DocumentFactory.getInstance().createDocument(rt);
			java.io.OutputStream os = new java.io.BufferedOutputStream(
				new java.io.FileOutputStream("size.xml"));
			try
			{
				org.dom4j.io.XMLWriter xmlWriter;
				xmlWriter = new org.dom4j.io.XMLWriter(os,
					new org.dom4j.io.OutputFormat("\t", true));
				xmlWriter.write(doc);
			} finally
			{
				os.close();
			}
		} catch(IOException e)
		{
			System.err.println("Could not save size");
			e.printStackTrace();
		}
	}

	void loadSize()
	{
		try
		{
			java.io.InputStream is = new java.io.BufferedInputStream(new java.io.FileInputStream(
				"size.xml"));
			org.dom4j.Document d;
			try
			{
				d = new org.dom4j.io.SAXReader().read(is);
			} finally
			{
				is.close();
			}
			Element sz = d.getRootElement();
			int x = Integer.parseInt(sz.attributeValue("x"));
			int y = Integer.parseInt(sz.attributeValue("y"));
			int w = Integer.parseInt(sz.attributeValue("width"));
			int h = Integer.parseInt(sz.attributeValue("height"));
			setBounds(x, y, w, h);
		} catch(Exception e)
		{
			System.err.println("Could not load size");
			e.printStackTrace();
		}
	}

	/**
	 * Runs the graphical version of Searcher
	 * 
	 * @param args The command line arguments--ignored
	 */
	public static void main(String [] args)
	{
		File.setDefaultArchiveDetector(new de.schlichtherle.io.DefaultArchiveDetector(
			"zip|jar|war|ear|tar|tar.gz|tgz|tzp|zip.rae|zip.raes|tar.bz2|tbz2"));
		SearcherGui gui = new SearcherGui();
		gui.setVisible(true);
	}
}
